//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe
// (email Richard Campbell at ulmont@bellsouth.net)
//

#ifndef _LINUX_GL_LIBRARIES
#define _LINUX_GL_LIBRARIES

#include <GL/glut.h>    				// Header File For The GLUT Library
#include <GL/gl.h>						// Header File For The OpenGL32 Library
#include <GL/glu.h>						// Header File For The GLu32 Library

#endif

#include <unistd.h>     				// Header file for sleeping.
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <math.h>

#include "texture.h"
#include "Player.h"

#define ESCAPE 27						// ascii code for the escape key

#define STAR_NUM 50 	                        // Number Of Stars To Draw

using namespace std;

typedef struct {
	float x, y, z;						// 3D Coordinates
	float u, v;                         // Texture Coordinates
} VERTEX;                               // Call It VERTEX

typedef struct {
	VERTEX vertex[3];					// Array Of Three Vertices
} TRIANGLE;								// Call It TRIANGLE

typedef struct {
	int numtriangles;                   // Number Of Triangles In Sector
	TRIANGLE* triangle;                 // Pointer To Array Of Triangles
} SECTOR;								// Call It SECTOR


int window;

bool* keyStates = new bool[256];


Player *player;

GLuint loop;
GLuint texture[3];
GLuint filter=0;

int light = 0;
int blend = 0;

bool fullscreen = true;

GLfloat xrot;
GLfloat yrot;
GLfloat xspeed;
GLfloat yspeed;
GLfloat xpos;
GLfloat zpos;

GLfloat heading;

GLfloat walkbias = 0.0f;
GLfloat walkbiasangle = 0.0f;

GLfloat lookupdown = 0.0f;
const float piover180 = 4*atan(1)/180.0;

GLfloat camx = 0.0f;
GLfloat camy = 0.0f;
GLfloat camz = 0.0f;
GLfloat therotate;

GLfloat z=0.0f;

GLfloat LightAmbient[]= { 0.3f, 0.5f, 0.8f, 1.0f };		// Ambient Light Values
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };     // Diffuse Light Values
GLfloat LightPosition[]= { 0.0f, 0.0f, 5.0f, 1.0f };    // Light Position

SECTOR sector1;

char files[2][16] = {
		"Data/world.txt",
		"Data/mud.bmp"
};

// degrees to radians...2 PI radians = 360 degrees
float rad(float angle){
	return angle * piover180;
}

// helper for SetupWorld.  reads a file into a string until a nonblank, non-comment line
// is found ("/" at the start indicating a comment); assumes lines < 255 characters long.
void readstr(FILE *f,char *string) {
	do {
		fgets(string, 255, f);
	} while ((string[0] == '/') || (string[0] == '\n'));
	return;
}

// loads the world from a text file.
void SetupWorld() {
	float x,y,z,u,v;
	int numtriangles;
	FILE *filein;
	char oneline[255];

	filein = fopen(files[0], "rt");

	readstr(filein,oneline);
	sscanf(oneline,"NUMPOLLIES %d\n", &numtriangles);

	sector1.numtriangles = numtriangles;                    	// Define The Number Of Triangles In Sector 1
	sector1.triangle = (TRIANGLE *) malloc(sizeof(TRIANGLE) * numtriangles);              // Allocate Memory For numtriangles And Set Pointer

	// Step Through Each Triangle In Sector
	for (int tri = 0; tri < numtriangles; tri++) {	// Loop Through All The Triangles
		// Step Through Each Vertex In Triangle
		for (int vert = 0; vert < 3; vert++) {		// Loop Through All The Vertices
			readstr(filein,oneline);							// Read String To Work With
			// Read Data Into Respective Vertex Values
			sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);	// Store Values Into Respective Vertices
			sector1.triangle[tri].vertex[vert].x = x;   // Sector 1, Triangle triloop, Vertice vertloop, x Value=x
			sector1.triangle[tri].vertex[vert].y = y;   // Sector 1, Triangle triloop, Vertice vertloop, y Value=y
			sector1.triangle[tri].vertex[vert].z = z;   // Sector 1, Triangle triloop, Vertice vertloop, z Value=z
			sector1.triangle[tri].vertex[vert].u = u;   // Sector 1, Triangle triloop, Vertice vertloop, u Value=u
			sector1.triangle[tri].vertex[vert].v = v;   // Sector 1, Triangle triloop, Vertice vertloop, v Value=v
		}
	}

	fclose(filein);
}

/*
 * getint and getshort are help functions to load the bitmap byte by byte on
 * SPARC platform (actually, just makes the thing work on platforms of either
 * endianness, not just Intel's little endian)
 */
unsigned int getInt(FILE *fp) {
	int c, c1, c2, c3;

	// get 4 bytes
	c = getc(fp);
	c1 = getc(fp);
	c2 = getc(fp);
	c3 = getc(fp);

	return ((unsigned int) c) +
			(((unsigned int) c1) << 8) +
			(((unsigned int) c2) << 16) +
			(((unsigned int) c3) << 24);
}

unsigned int getShort(FILE *fp) {
	int c, c1;

	//get 2 bytes
	c = getc(fp);
	c1 = getc(fp);

	return ((unsigned int) c) + (((unsigned int) c1) << 8);
}

bool LoadGLTextures(char *filename) {
	// Load Texture
	Image *image1;

	// allocate space for texture
	image1 = (Image *) malloc(sizeof(Image));
	if (image1 == NULL) {
		cout << "Error allocating space for image" << endl;
		return false;
	}

	if (!ImageLoad(filename, image1)) {
		return false;
	}

	glGenTextures(3, &texture[0]);				// Create Texture

	// Create Nearest Filtered Texture
	glBindTexture(GL_TEXTURE_2D, texture[0]);   // 2d texture (x and y size)
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // less cpu, less quality, more frame per seconds
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	// Create Linear Filtered Texture
	glBindTexture(GL_TEXTURE_2D, texture[1]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	// Create MipMapped Texture
	glBindTexture(GL_TEXTURE_2D, texture[2]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image1->sizeX, image1->sizeY, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	if (image1){
		if (image1->data){
			free(image1->data);
		}
		free(image1);
	}
	return true;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
bool InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
	LoadGLTextures(files[1]);
	glEnable(GL_TEXTURE_2D);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE);		// Set the blending function for translucency (note off at init time)
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);	// This Will Clear The Background Color To Black
	glClearDepth(1.0);						// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);					// type of depth test to do.
	glEnable(GL_DEPTH_TEST);				// enables depth testing.
	glShadeModel(GL_SMOOTH);				// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();						// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);

	// set up lights.
	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
	glEnable(GL_LIGHT1);

	return true;
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
GLvoid ReSizeGLScene(GLsizei Width, GLsizei Height)
{
	if (Height==0)							// Prevent A Divide By Zero If The Window Is Too Small
		Height=1;

	glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window
	glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
GLvoid DrawGLScene()
{
	GLfloat x_m, y_m, z_m, u_m, v_m;
	GLfloat xtrans, ytrans, ztrans;
	GLfloat sceneroty;
	int numtriangles;

	// calculate translations and rotations.
	xtrans = -player->getXpos();
	ztrans = -player->getZpos();
	ytrans = -player->getWalkbiasangle()-0.25f;
	sceneroty = 360.0f - player->getHeadingH();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
	glLoadIdentity();

	glRotatef(player->getHeadingV(), 1.0f, 0, 0);
	glRotatef(sceneroty, 0, 1.0f, 0);

	glTranslatef(xtrans, ytrans, ztrans);

	glBindTexture(GL_TEXTURE_2D, texture[filter]);    // pick the texture.

	numtriangles = sector1.numtriangles;

	for (int loop=0; loop<numtriangles; loop++) {        // loop through all the triangles
		glBegin(GL_TRIANGLES);
		glNormal3f( 0.0f, 0.0f, 1.0f);

		x_m = sector1.triangle[loop].vertex[0].x;
		y_m = sector1.triangle[loop].vertex[0].y;
		z_m = sector1.triangle[loop].vertex[0].z;
		u_m = sector1.triangle[loop].vertex[0].u;
		v_m = sector1.triangle[loop].vertex[0].v;
		glTexCoord2f(u_m,v_m);
		glVertex3f(x_m,y_m,z_m);

		x_m = sector1.triangle[loop].vertex[1].x;
		y_m = sector1.triangle[loop].vertex[1].y;
		z_m = sector1.triangle[loop].vertex[1].z;
		u_m = sector1.triangle[loop].vertex[1].u;
		v_m = sector1.triangle[loop].vertex[1].v;
		glTexCoord2f(u_m,v_m);
		glVertex3f(x_m,y_m,z_m);

		x_m = sector1.triangle[loop].vertex[2].x;
		y_m = sector1.triangle[loop].vertex[2].y;
		z_m = sector1.triangle[loop].vertex[2].z;
		u_m = sector1.triangle[loop].vertex[2].u;
		v_m = sector1.triangle[loop].vertex[2].v;
		glTexCoord2f(u_m,v_m);
		glVertex3f(x_m,y_m,z_m);

		glEnd();
	}
	glutSwapBuffers();						// since this is double buffered, swap the buffers to display what just got drawn.
}

void switchFullScreen(){
	if (fullscreen){
		glutFullScreen();
	} else {
		glutReshapeWindow(640,480);
		glutPositionWindow(0,0);
	}
	fullscreen = !fullscreen;
}

void specialKeyPressed(int key, int x, int y){
	//usleep(100);

	switch(key){
	case GLUT_KEY_F1 :
		switchFullScreen();
		break;

	case GLUT_KEY_UP: 				// look up
		player->LookUp();
		break;

	case GLUT_KEY_DOWN: 			// look down
		player->LookDown();
		break;

	case GLUT_KEY_LEFT: 			// look left
		player->LookLeft();
		break;

	case GLUT_KEY_RIGHT: 			// look right
		player->LookRight();
		break;

	default:
		cout << "Special key " << (char)key << " pressed. No action there yet." << endl;
		break;
	}
}

void keyUp(unsigned char key, int x, int y){
	keyStates[key] = false;
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) {
	keyStates[key] = true;
}
void keyOperation () {
	keyStates[key] = true;
	//usleep(100);

	switch(key){
	case ESCAPE :
		cout << "Exit." << endl;
		glutDestroyWindow(window);
		exit(0);
		break;

	case 'w':
		player->WalkForward();
		break;
	case 'a':
		player->StrafeLeft();
		break;
	case 's':
		player->WalkBackward();
		break;
	case 'd':
		player->StrafeRight();
		break;

	case 'b':
		player->ToggleBounce();
		break;

	case 'B': // switch the blending
		cout << "B/b pressed; blending is: " << blend << endl;
		blend = blend ? 0 : 1;              // switch the current value of blend, between 0 and 1.
		if (blend) {
			glEnable(GL_BLEND);
			glDisable(GL_DEPTH_TEST);
		} else {
			glDisable(GL_BLEND);
			glEnable(GL_DEPTH_TEST);
		}
		cout << "Blending is now: " << blend << endl;
		break;

	case 'f':
	case 'F': // switch the filter
		cout << "F/f pressed; filter is: " << filter << endl;
		filter++;                           // switch the current value of filter, between 0/1/2;
		if (filter > 2) {
			filter = 0;
		}
		cout << "Filter is now: " << filter << endl;
		break;

	case 'l':
	case 'L': // switch the lighting
		cout << "L/l pressed; lighting is: " << light << endl;
		light = light ? 0 : 1;              // switch the current value of light, between 0 and 1.
		if (light) {
			glEnable(GL_LIGHTING);
		} else {
			glDisable(GL_LIGHTING);
		}
		cout << "Lighting is now: " << light << endl;
		break;

	default:
		cout << "Key " <<  (char)key << " pressed. No action there yet.\n" << endl;;
		break;
	}
}

int main(int argc, char **argv)
{
	player = new Player();

	SetupWorld();

	/* Initialize GLUT state - glut will take any command line arguments that pertain to it or
     X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */
	glutInit(&argc, argv);

	/* Select type of Display mode:
     Double buffer
     RGBA color
     Alpha components supported
     Depth buffer */
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);

	/* get a 640 x 480 window */
	glutInitWindowSize(640, 480);

	/* the window starts at the upper left corner of the screen */
	glutInitWindowPosition(0, 0);

	/* Open a window */
	window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");

	/* Register the function to do all our OpenGL drawing. */
	glutDisplayFunc(&DrawGLScene);

	/* Go fullscreen.  This is as soon as possible. */
	//glutFullScreen();
	fullscreen = false;

	/* Even if there are no events, redraw our gl scene. */
	glutIdleFunc(&DrawGLScene);

	/* Register the function called when our window is resized. */
	glutReshapeFunc(&ReSizeGLScene);

	/* Register the function called when the keyboard is pressed. */
	glutKeyboardFunc(&keyPressed);
	glutKeyboardUpFunc(&keyUp);

	/* Register the function called when special keys (arrows, page down, etc) are pressed. */
	glutSpecialFunc(&specialKeyPressed);

	/* Initialize our window. */
	InitGL(640, 480);

	/* Start Event Processing Engine */
	glutMainLoop();

	return 1;
}

